package com.woniu.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.woniu.entity.*;
import com.woniu.mapper.*;
import com.woniu.service.TestDetailService;
import com.woniu.service.elasticSearch.ElasticsearchForTest;
import com.woniu.service.util.SSHClient;
import com.woniu.service.vo.ShowTestVo;
import com.woniu.service.vo.TestPageVo;
import com.woniu.service.vo.TestVo;
import com.woniu.util.SnowFlow;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author D
 * @since 2023-03-27 08:24:57
 */
@Service
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class TestDetailServiceImpl extends ServiceImpl<TestDetailMapper, TestDetail> implements TestDetailService {
    @Autowired
    private TestDetailMapper testDetailMapper;
    @Autowired
    private TestOutsideMapper testOutsideMapper;
    @Autowired
    private TestTypeMapper typeMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private CollectAndTestMapper collectAndTestMapper;
    @Autowired
    private CommentMapper commentMapper;
    @Autowired
    private FootprintMapper footprintMapper;
    @Autowired
    private TestTypeMapper testTypeMapper;
    @Autowired
    private SSHClient sshClient;

    @Autowired
    ElasticsearchForTest elasticsearchForTest;
    private SnowFlow snowFlow = new SnowFlow();

    private final String PATH = "http://192.168.201.41:8004/imgs/";
    /**
     * 获取靶场详情
     *
     * @param testId 靶场编号
     * @return 返回靶场详情对象
     */
    @Override
    public TestVo getTestDetail(Long testId) {
        //获取靶场详情
        TestDetail testDetail = testDetailMapper.selectById(testId);
        TestOutside testOutside = testOutsideMapper.selectById(testId);
        List<TestType> testTypes = typeMapper.selectList(null);
        TestVo oldTestVo = new TestVo();
        if (testDetail == null || testOutside == null) {
            return null;
        }
        //将靶场详情转换为靶场对象
        TestVo testVo = oldTestVo.testDetailToTestVo(testDetail, oldTestVo);
        testVo = testVo.testOutsideToTestVo(testOutside, testVo);
        TestVo finalTestVo = testVo;
        testTypes.forEach(testType -> {
            if (testType.getTypeId().equals(testDetail.getTestTypeId())) {
                finalTestVo.setTestType(testType.getTypeName());
            }
        });
        switch (testOutside.getDifficultyScore()) {
            case 5:
                finalTestVo.setDifficulty("初级");
                break;
            case 10:
                finalTestVo.setDifficulty("中级");
                break;
            case 15:
                finalTestVo.setDifficulty("高级");
                break;
            default:
                finalTestVo.setDifficulty("未知");
                break;
        }
        return finalTestVo;
    }
    /**
     * 添加一个靶场详情。
     *
     * @param userCode 用户名
     * @param testVo 靶场对象
     * @return 添加成功返回 true，否则返回 false。
     * @throws Exception 如果发生异常，则回滚事务。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addTestDetail(Long userCode, TestVo testVo) {
        Boolean result = false;
        //自动生成testCode
        Long testCode = snowFlow.nextId();
        //处理url
        String courseUrl = PATH + testVo.getCourseUrl();
        String pictureUrl = PATH + testVo.getTestPictureUrl();
        String testTypeUrl =PATH + testVo.getTestUrl();
        String flag = "flag";
        //判断靶场文件类型
        Integer fileTypeId = 4;
        if (testTypeUrl.endsWith(".tar") || testTypeUrl.endsWith(".tar.gz")) {
            fileTypeId = 1;
        } else if (testTypeUrl.endsWith(".war")) {
            fileTypeId = 2;
        } else if (testTypeUrl.endsWith(".rar")) {
            fileTypeId = 3;
        }
        int addTestDetail = -1;
        int addTestOutside = -1;
        //添加靶场详情
        TestDetail testDetail = new TestDetail(testCode, testVo.getTestName(), testVo.getIntroduce(), courseUrl,
                testTypeUrl, flag, testVo.getTestTypeId(),userCode, testVo.getSimpleNote(), fileTypeId);
        addTestDetail = testDetailMapper.insert(testDetail);
        TestOutside testOutside = new TestOutside(testCode, testVo.getTestName(), pictureUrl, testVo.getDifficult()
                , 0, 0, new Date(), new Date(), 0, 0);
        addTestOutside = testOutsideMapper.insert(testOutside);

        if (addTestDetail == 1 && addTestOutside == 1) {
            elasticsearchUpdate(testOutside,testDetail,testVo.getTestTypeId());
            result = true;
        }
        return result;
    }

    /**
     * 根据用户名获取该用户创建的所有靶场详情，可根据靶场名称模糊查询，返回分页结果
     *
     * @param userCode 用户userCode
     * @param input    模糊查询输入的靶场名称
     * @param current  当前页码
     * @param size     每页显示数量
     * @return 返回包含分页信息和靶场详情列表的TestPageVo对象
     */
    @Override
    public TestPageVo getAllTestDetail(Long userCode, String input, Long current, Long size) {

        Page<TestDetail> page = new Page<>(current, size);
        QueryWrapper<TestDetail> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("creator_code", userCode);
        queryWrapper.orderByDesc("test_code");
        if (!StringUtils.isEmpty(input)) {
            queryWrapper.like("test_name", input);
        }
        //获取靶场详情
        Page<TestDetail> testDetails = testDetailMapper.selectPage(page, queryWrapper);
        if(testDetails==null||testDetails.getRecords().size()==0){
            return null;
        }
        List<Long> collect = testDetails.getRecords().stream().map(o -> {
            return o.getTestCode();
        }).collect(Collectors.toList());
        QueryWrapper<TestOutside> testOutsideQueryWrapper = new QueryWrapper<>();
        testOutsideQueryWrapper.in("test_code", collect);
        testOutsideQueryWrapper.orderByDesc("test_code");
        List<TestOutside> testOutsides = testOutsideMapper.selectList(testOutsideQueryWrapper);
        if (testOutsides==null||testOutsides.size()==0){
            return null;
        }
        //循环判断testCode相同存入testVo
        List<TestVo> collect1 = testOutsides.stream().map(testOutside -> {
            TestVo testVo = new TestVo();
            testDetails.getRecords().forEach(testDetail -> {
                if (testDetail.getTestCode().equals(testOutside.getTestCode())) {
                    testVo.testOutsideToTestVo(testOutside, testVo);
                    testVo.testDetailToTestVo(testDetail, testVo);
                }
            });
            return testVo;
        }).collect(Collectors.toList());
        TestPageVo testPageVo = new TestPageVo<>();
        testPageVo.setPageVo(collect1);
        testPageVo.setPages(testDetails.getPages());
        testPageVo.setCurrentPage(testDetails.getCurrent());
        testPageVo.setSize(testDetails.getSize());
        testPageVo.setTotal(testDetails.getTotal());
        return testPageVo;
    }
    /**
     * 根据靶场code删除靶场详情及相关数据
     *
     * @param testCode 靶场code
     * @return 删除结果，成功返回 true，失败返回 false
     * @throws Exception 删除过程中发生的异常
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteTestDetail(Long testCode) {
        Boolean result = false;
        QueryWrapper<TestDetail> testDetailQueryWrapper = new QueryWrapper<>();
        testDetailQueryWrapper.eq("test_code", testCode);
        int testDetail = testDetailMapper.delete(testDetailQueryWrapper);

        QueryWrapper<TestOutside> testOutsideQueryWrapper = new QueryWrapper<>();
        testOutsideQueryWrapper.eq("test_code", testCode);
        int testOutside = testOutsideMapper.delete(testOutsideQueryWrapper);

        QueryWrapper<CollectAndTest> collectAndTestQueryWrapper = new QueryWrapper<>();
        collectAndTestQueryWrapper.in("test_code", testCode);
        List<CollectAndTest> collectAndTests = collectAndTestMapper.selectList(collectAndTestQueryWrapper);
        if (!StringUtils.isEmpty(collectAndTests)) {
            collectAndTestMapper.delete(collectAndTestQueryWrapper);
        }

        QueryWrapper<Comment> commentQueryWrapper = new QueryWrapper<>();
        commentQueryWrapper.in("test_code", testCode);
        List<Comment> comments = commentMapper.selectList(commentQueryWrapper);
        if (!StringUtils.isEmpty(comments)) {
            commentMapper.delete(commentQueryWrapper);
        }

        QueryWrapper<Footprint> footprintQueryWrapper = new QueryWrapper<>();
        footprintQueryWrapper.in("test_code", testCode);
        List<Footprint> footprints = footprintMapper.selectList(footprintQueryWrapper);
        if (!StringUtils.isEmpty(footprints)) {
            footprintMapper.delete(footprintQueryWrapper);
        }
        if (testDetail == 1 && testOutside == 1) {
            elasticsearchForTest.deleteById(testCode);
            result = true;
        }
        return result;
    }
    /**
     * 根据靶场code获取详细信息
     *
     * @param testCode 靶场code
     * @return 包含靶场详细信息的TestVo对象
     */
    @Override
    public TestVo getOneTestDetail(Long testCode) {

        QueryWrapper<TestDetail> testDetailQueryWrapper = new QueryWrapper<>();
        testDetailQueryWrapper.eq("test_code", testCode);
        TestDetail testDetail = testDetailMapper.selectOne(testDetailQueryWrapper);

        QueryWrapper<TestOutside> testOutsideQueryWrapper = new QueryWrapper<>();
        testOutsideQueryWrapper.eq("test_code", testCode);
        TestOutside testOutside = testOutsideMapper.selectOne(testOutsideQueryWrapper);

        QueryWrapper<TestType> testTypeQueryWrapper = new QueryWrapper<>();
        testTypeQueryWrapper.eq("type_id", testDetail.getTestTypeId());
        TestType testType = testTypeMapper.selectOne(testTypeQueryWrapper);
        Integer[] testTypeArray = {testType.getParentType(), testType.getTypeId()};

        TestVo testVo = new TestVo();
        testVo.setTestCode(testDetail.getTestCode().toString());
        testVo.setTestName(testDetail.getTestName());
        testVo.setSimpleNote(testDetail.getTestNote());
        testVo.setIntroduce(testDetail.getIntroduce());
        testVo.setTestUrl(testDetail.getTestUrl());
        testVo.setCourseUrl(testDetail.getCourseUrl());
        testVo.setFlag(testDetail.getFlag());
        testVo.setTestPictureUrl(testOutside.getTestPictureUrl());
        testVo.setDifficult(testOutside.getDifficultyScore());
        testVo.setTestTypeArray(testTypeArray);
        return testVo;
    }
    /**
     * 更新靶场详情
     *
     * @param testVo 靶场对象
     * @return true - 更新成功，false - 更新失败
     * @throws Exception 更新失败抛出异常
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateTestDetail(TestVo testVo) {
        Boolean result = false;
        int testDetailResult = -1;
        int testOutsideResult = -1;

        String testPicture = testVo.getTestPictureUrl();
        String testCourse = testVo.getCourseUrl();
        //判断是否修改图片
        if (!testPicture.startsWith("http")) {
            testPicture = PATH + testPicture;
        }
        if (!testCourse.startsWith("http")) {
            testCourse = PATH+ testCourse;
        }

        QueryWrapper<TestDetail> testDetailQueryWrapper = new QueryWrapper<>();
        testDetailQueryWrapper.eq("test_code", testVo.getTestCode());
        TestDetail testDetail = testDetailMapper.selectOne(testDetailQueryWrapper);
        if (!StringUtils.isEmpty(testDetail)) {
            testDetail.setTestName(testVo.getTestName());
            testDetail.setTestNote(testVo.getSimpleNote());
            testDetail.setIntroduce(testVo.getIntroduce());
            testDetail.setCourseUrl(testCourse);
            testDetail.setTestTypeId(testVo.getTestTypeId());
            testDetailResult = testDetailMapper.update(testDetail, testDetailQueryWrapper);
        }

        QueryWrapper<TestOutside> testOutsideQueryWrapper = new QueryWrapper<>();
        testOutsideQueryWrapper.eq("test_code", testVo.getTestCode());
        TestOutside testOutside = testOutsideMapper.selectOne(testOutsideQueryWrapper);
        if (!StringUtils.isEmpty(testOutside)) {
            testOutside.setTestName(testVo.getTestName());
            testOutside.setTestPictureUrl(testPicture);
            testOutside.setDifficultyScore(testVo.getDifficult());
            testOutsideResult = testOutsideMapper.update(testOutside, testOutsideQueryWrapper);
        }

        if (testDetailResult == 1 && testOutsideResult == 1) {
           elasticsearchUpdate(testOutside,testDetail,testVo.getTestTypeId());
            result = true;
        }
        return result;
    }
    private void elasticsearchUpdate(TestOutside testOutside,TestDetail testDetail ,Integer testTypeId){
        QueryWrapper<TestType> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("type_id",testTypeId);
        TestType testType = testTypeMapper.selectOne(queryWrapper);
        ShowTestVo showTestVo = new ShowTestVo(testOutside,testDetail,testType);
        elasticsearchForTest.update(showTestVo);
    }

    /**
     * 关闭靶场环境，停止对应的Docker容器，并删除Redis缓存中与该靶场环境相关的键值对
     * @param testId 靶场code
     * @param userCode 用户code
     * @throws Exception 当连接SSH服务器或执行SSH命令失败时，抛出异常
     */
    @Override
    public void closeTest(long testId, Long userCode) throws Exception{
        sshClient.connect("192.168.201.41",8007,"root","123456");
        String execute = "docker stop "+userCode+" && docker rm -f "+userCode;
        execute = sshClient.executeCommand(execute);
        String ip = (String) redisTemplate.opsForValue().get("-" + userCode + "port" + testId);
        if (!StringUtils.isEmpty(ip)){
            String port = ip.split(":")[2];
            redisTemplate.opsForSet().remove("port",Integer.parseInt(port));
        }
        redisTemplate.delete("-"+userCode + "-flag-" + testId);
        redisTemplate.delete("-"+userCode + "-flag-" + testId + "-time");
        redisTemplate.delete("-"+userCode + "startTest" + testId);
        redisTemplate.delete("-"+userCode + "-testDelay-" + testId);
        redisTemplate.delete("-"+userCode + "port" + testId);
        log.info("关闭测试环境  回显信息：{}",execute);
    }
    /**
     * 启动靶场环境，创建Docker容器并将容器的端口映射到指定端口上，并获取容器的IP地址
     * @param testId 靶场code
     * @param userCode 用户code
     * @return 包含IP地址和端口号的Map对象
     * @throws Exception 当连接SSH服务器或执行SSH命令失败时，抛出异常
     */
    @Override
    public Map<String, String> startTest(Long testId, Long userCode)throws Exception {
        sshClient.connect("192.168.201.41",8007,"root","123456");
        Integer port = 8000;
        port = getPort(port);
        String execute = "docker run -p "+port+":9292 --name "+userCode+" -d test:01 && docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "+userCode;
        String result = sshClient.executeCommand(execute);
        String[] split = result.split("\n");
        if (split.length >1){
            result = split[1];
        }
        Map<String, String> map = new HashMap<>();
        map.put("ip",result);
        map.put("port",port.toString());
        return map;
    }
    private synchronized Integer getPort(Integer port) {
        Boolean port1 = redisTemplate.opsForSet().isMember("port", port);
        if (port1!= null && port1 && port < 9999) {
            port++;
            port = getPort(port);
        }
        redisTemplate.opsForSet().add("port", port);
        return port;
    }
    /**
     * 根据用户完成情况，更新用户分数
     *
     * @param userCode 用户名
     * @param testCode 靶场code
     * @return 若靶场已经完成过则返回false，否则返回true
     */
    @Override
    public boolean calculateByScore(Long userCode, Long testCode) {
        int score = 0;
        User user = userMapper.selectOne(new QueryWrapper<User>().eq("user_code", userCode));
        Boolean isSeeAnswer = (Boolean) redisTemplate.opsForValue().get("-"+userCode + "seeAnswer" + testCode);

        TestDetail testDetail = testDetailMapper.selectOne(new QueryWrapper<TestDetail>().eq("test_code", testCode));
        TestOutside testOutside = testOutsideMapper.selectOne(new QueryWrapper<TestOutside>().eq("test_code", testCode));
        QueryWrapper<TestType> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("type_id", testDetail.getTestTypeId());
        TestType testType = typeMapper.selectOne(queryWrapper);
        if (testType.getParentType() == 1) {
            score = 1;
        } else if (isSeeAnswer == null || isSeeAnswer) {
            score = testOutside.getDifficultyScore();
        } else {
            score = testOutside.getDifficultyScore() / 3;
        }
        if (isTested(user.getUserCode(), testCode, score)) {
            return false;
        }
        testOutside.setVisitors(testOutside.getVisitors() + 1);
        testOutsideMapper.updateById(testOutside);
        user.setTotalScore(user.getTotalScore() + score);
        userMapper.updateById(user);
        return true;
    }

    /**
     * 判断用户是否已经完成该靶场，若未完成则添加靶场记录
     *
     * @param userCode 用户code
     * @param testCode 考试code
     * @param score    用户得分
     * @return 若已经完成过靶场则返回true，否则添加靶场记录并返回false
     */
    private boolean isTested(Long userCode, Long testCode, int score) {
        QueryWrapper<CollectAndTest> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("test_code", testCode);
        queryWrapper.eq("user_code", userCode);
        CollectAndTest collectAndTests = collectAndTestMapper.selectOne(queryWrapper);
        CollectAndTest collectAndTest = new CollectAndTest();
        collectAndTest.setTestCode(testCode);
        collectAndTest.setUserCode(userCode);
        collectAndTest.setTestTime(new Date());
        if (collectAndTests == null) {
            collectAndTest.setCollectType(2);
            collectAndTest.setScore(score);
            collectAndTestMapper.insert(collectAndTest);
            return false;
        }
        collectAndTest.setId(collectAndTests.getId());
        if (collectAndTests.getCollectType() == 1) {
            collectAndTest.setCollectType(3);
            collectAndTestMapper.updateById(collectAndTest);
            return false;
        }
        collectAndTest.setCollectType(collectAndTests.getCollectType());
        collectAndTestMapper.updateById(collectAndTest);
        return true;
    }

}
